/*
 * Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
 * All rights reserved.
 *
 * This file is part of Harmattan SmashMiner OpenGL game application.
 * 
 * Harmattan SmashMiner OpenGL Example Application version 1.0.0
 *  
 * Latest update: 15.4.2011
 *
 * The Harmattan SmashMiner OpenGL example application demonstrates how to use 
 * the OpenGL ES in Harmattan devices.
 *
 * This example is provided as a starting point for 3rd party
 * developers (you) to ease the implementation of OpenGL based 
 * games. Ideas, parts of code or methodologies that this
 * example application uses can be freely used without restrictions.
 *
 * See file README.txt how to build and compile this example application
 * in the Harmattan SDK environment. 
 * 
 * See file INSTALL.txt to find out what is required to run this
 * application and how to install the application to the device or
 * alternatively in QEMU-emulator.
 *
 * The LICENSE.txt file explains the license this software is provided
 * with. A copy of the same license is included in this source file.
 * 
 */

/*
 * Copyright (C) 2011 by Nokia Corporation.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "missile.h"
#include <math.h>
#include <qmath.h>
#include "gameengine.h"
#include <QDebug>

/*! \class Missile
 *
 * \c Missile class to implement missiles fired by UFO's derived from \c GameObject and
 * \c RotatingObject. The missile direction angle is implemented using the properties form
 * \c RotatingObject.
 */

/*! Constructor for Missile class
 *
 * \param bornFrame         Current game frame number when game object was born.
 * \param rewindEnabled     Is the rewind mode playback enabled.
 * \param guidanceFactor    Guidance factor of the missile, how steep is the guidance towards
 *                          target (miner).
 * \param speedFactor       Speed factor of the missile, how fast is the missile movement.
 */
Missile::Missile(int bornFrame, bool rewindEnabled, FPtype guidanceFactor, FPtype speedFactor)
    : GameObject(TypeMissile, bornFrame, GameEngine::GameObjectRadiusMissile, SpeedLimitExtraHigh, 
                 rewindEnabled, MassTiny, ScoreMissile)
    , guidance(guidanceFactor)
    , speed(speedFactor)
{
    setRotationSpeed(0.0f);
    setRotationAxis(0.0f, 0.0f, 1.0f);
}

/*! Initializes the missile direction according to given direction vector.
 * Missile direction is set using the \c Missile::setDirection() method.
 *
 * \param dir   Direction vector with X- and Y-components in indexes 0 and 1.
 */
void Missile::initDirection(const FPtype *dir)
{
    setDirection(dir, true);
}

/*! Adjusts the missile direction according to given direction vector.
 * Missile direction is set using the \c Missile::setDirection() method.
 *
 * \param dir   Direction vector with X- and Y-components in indexes 0 and 1.
 */
void Missile::adjustDirection(const FPtype *dir)
{
    setDirection(dir);
}

/*! Sets the missile direction according to given direction vector.
 * Initializes the missile direction if reset is requested.
 *
 * Direction vector is a vector from missile to miner. The distance between
 * missile and miner is calculated according to pythagorean theorem from X- and
 * Y-components \f$c = \sqrt{a^2 + b^2}\f$. 
 *
 * \code
 * dist = sqrt(dir[0] * dir[0] + dir[1] * dir[1]);
 * \endcode
 *
 * The direction vector is normalized into a unit vector by dividing the components with distance:
 *
 * \code
 * dirXNorm = dir[0] / dist;
 * dirYNorm = dir[1] / dist;
 * \endcode
 *
 * Since the unit vector is created by dividing with distance, it's important to check that the
 * distance is not zero or too close to zero. If the distance is under 0.1 we just set the 
 * rotation angle to 0.0.
 *
 * If the reset was not requested the missile direction will be adjusted according
 * to given direction and guidance factor. Unit vector for current velocity is calculaated:
 *
 * \code
 * currVelDist = sqrt(currVelX * currVelX + currVelY * currVelY);
 * currVelXNorm = currVelX / currVelDist;
 * currVelYNorm = currVelY / currVelDist;
 * \endcode
 *
 * Guidance vector is calculated using distance vector normal and guidance.
 * \code
 * guidX = (guidance / 20.0f) * dirXNorm;
 * guidY = (guidance / 20.0f) * dirYNorm;
 * \endcode
 *
 * Adjusted direction vector is summed from current velocity unit vector and guidance vector:
 * \code
 * fixDirX = currVelXNorm + guidX;
 * fixDirY = currVelYNorm + guidY;
 * \endcode
 *
 * A unit vector for adjusted direction is calculated:
 * \code
 * fixVelDist = sqrt(fixDirX * fixDirX + fixDirY * fixDirY);
 * dirXNorm = fixDirX / fixVelDist;
 * dirYNorm = fixDirY / fixVelDist;
 * \endcode
 *
 * Rotation angle according to direction vector is calculated always.
 * Direction vector is either adjusted direction vector as described above or
 * if reset was requested, an unit vector calculated from given direction vector.
 * 
 * - Angle between two vectors can be calculated from
 *
 *   \f$ A \cdot B = |A| * |B| * cos(\theta) \f$ 
 *
 * - Angle between two _unit_ vectors can be calculated from
 *
 *   \f$ A \cdot B = 1 * 1 * cost(\theta) \f$
 *
 * - so
 *
 *   \f$ \theta = acos(A \cdot B) \f$
 *
 * Dot product: \f$ A \cdot B = A_1B_1 + \cdots + A_nB_n \f$
 *
 * Missile default direction (rotation angle 0.0) is along the Y-axis, tail in origo.
 * So the dot product between y-axis (the initial direction of missile is along the y-axis) 
 * and the direction vector is calculated.
 *
 * \code
 * dirXNorm * 0 + dirYNorm * 1
 * \endcode
 *
 * So the dot product equals to \c dirYNorm.
 *
 * If \c dirXNorm is near zero or zero and \c dirYNorm is positive the angle is the default 
 * angle. If \c dirYNorm is negative the angle is rotated 180°.
 *
 * If \c dirYNorm is near zero or zero and \c dirXNorm is positive the rotation angle is 
 * -90°. If \c dirXNorm is negative the rotation angle is 90°.
 *
 * Otherwise the angle between y-axis and direction vector is calculated.
 *
 * \code
 * a = acos(dirYNorm) * 180.0f / M_PI;
 * \endcode
 *
 * The missile rotation angle may still need to be fixed --- the possible values for \c a 
 * are from -90° to 90°: 
 * - when a < 0 the angle is > 90°
 * - when a > 0 the angle is < 90°
 *
 * When \c a > 0.0f the angle is < 90° and the rotation direction is correct, 
 * otherwise when \c dirXNorm > 0.0 the rotation direction must be set to negative.
 * When \c a < 0.0f the angle is > 90°, also the rotation direction is fixed when \c dirXNorm > 0.0:
 *
 * \code
 * if (a > 0.0f && dirXNorm > 0.0f)
 *      a = -a;
 * else if (a < 0.0f && dirXNorm > 0.0f)
 *      a = -90.0f + a;
 * else if (a < 0.0f && dirXNorm < 0.0f)
 *      a = 90.0f - a;
 * \endcode
 *
 * \param dir       Direction vector with X- and Y-components in indexes 0 and 1.
 * \param reset     If set to ''true'' the missile direction is initialized 
 *                  otherwise the missile direction is adjusted.
 */
void Missile::setDirection(const FPtype *dir, bool reset)
{
    const FPtype dist = sqrt(dir[0] * dir[0] + dir[1] * dir[1]);

    if (dist < 0.1) {
        qDebug() << "Distance vector is small!";
        setRotationAngle(0.0f);
        return;
    }

    // Normalize the direction vector to unit vector
    FPtype dirXNorm = dir[0] / dist;
    FPtype dirYNorm = dir[1] / dist;

    // Fix current direction according to new direction vector. 
    // If guidanceFactor is 1.0 or greater, the original
    // direction will be omitted and the direction vector will be
    // used as is.
    if (!reset) { 
        const FPtype currVelX = velX();
        const FPtype currVelY = velY();
        const FPtype currVelDist = sqrt(currVelX * currVelX + currVelY * currVelY);
        const FPtype currVelXNorm = currVelX / currVelDist;
        const FPtype currVelYNorm = currVelY / currVelDist;

        const FPtype guidX = (guidance / 20.0f) * dirXNorm;
        const FPtype guidY = (guidance / 20.0f) * dirYNorm;

        const FPtype fixDirX = currVelXNorm + guidX;
        const FPtype fixDirY = currVelYNorm + guidY;

        const FPtype fixVelDist = sqrt(fixDirX * fixDirX + fixDirY * fixDirY);
        dirXNorm = fixDirX / fixVelDist;
        dirYNorm = fixDirY / fixVelDist;
    }

    // Calculate the missile rotation according to direction vector.
    //
    // Angle between two vectors can bel calculated from
    //
    //  A · B = |A| * |B| * cos(Θ) 
    //
    // - angle between two _unit_ vectors can be calculated 
    //      
    //          A · B = 1 * 1 * cos(Θ)
    // - so
    //          Θ = acos(A · B)
    //
    // dot product: A · B = A1B1 + ... + AnBn
    //
    // In this case we calculate dot product between y-axis (the initial
    // direction of missile is along the y-axis) and the direction vector.
    //
    //     dirXNorm * 0 + dirYNorm * 1;
    //
    // So the dot product equals to 'dirYNorm'.

    FPtype a;

    // When rotation angle of missile is set to 0.0 the direction of missile 
    // is along the y-axis, tail in origo.
    if (fabs(dirXNorm) < 0.0001f) {
        if (dirYNorm > 0.0f)
            a = 0.0f;
        else  
            a = 180.0f;
    }
    else if (fabs(dirYNorm) < 0.0001f) {
        if (dirXNorm > 0.0f)
            a = -90.0f;
        else 
            a = 90.0f;
    }
    else {
        // a is angle between y-axis and direction vector
        // a values are from -90° to +90°
        // when a < 0 the angle is > 90°
        // when a > 0 the angle is < 90°
        a = acos(dirYNorm) * 180.0f / M_PI;

        // when a > 0.0f the angle is < 90° and the rotation direction is correct 
        // otherwise when dirX > 0.0 the rotation direction must be set to negative 
        if (a > 0.0f && dirXNorm > 0.0f) 
            a = -a;
        // when a < 0.0f the angle is > 90°, also fix the rotation direction when dirX > 0.0
        else if (a < 0.0f && dirXNorm > 0.0f)
            a = -90.0f + a;
        else if (a < 0.0f && dirXNorm < 0.0f)
            a = 90.0f - a;
    }

    setVelX(dirXNorm * speed);
    setVelY(dirYNorm * speed);
    setRotationAngle(a);
}

#ifdef SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

/*! Set the missile direction in rewind mode.
 * The missile direction in rewind mode playback is calculated using
 * a direction vector calculated from missile location in current frame
 * and previous frame. The direction is set using \c Missile::setDirection()
 * accordingly.
 *
 * \param frame     Current frame in rewind mode playback.
 * \param reset     If set to ''true'' the missile direction is initialized 
 *                  otherwise the missile direction is adjusted.
 */
void Missile::setRewindDirection(int frame, bool reset)
{
    GLfloat curPos[2];
    GLfloat prvPos[2];
    readPosition(frame, curPos);
    readPosition(frame - 1, prvPos);

    const FPtype dir[] = {
        curPos[0] - prvPos[0],
        curPos[1] - prvPos[1]
    };

    setDirection(dir, reset ? 1.0f : guidance);
}

/*! Initialize the missile direction in rewind mode.
 *
 * \param frame     Current frame in rewind mode playback.
 */
void Missile::initRewindDirection(int frame)
{
    setRewindDirection(frame, true);
}

/*! Adjust the missile direction in rewind mode.
 *
 * \param frame     Current frame in rewind mode playback.
 */
void Missile::adjustRewindDirection(int frame)
{
    setRewindDirection(frame);
}

#endif  // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE
